package org.oos.atom;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;

import org.oos.atom.entries.OosEntry;
import org.oos.auth.AuthenticationHandler;
import org.oos.dao.options.OptionsMap;
import org.oos.utils.OosLogger;

import com.google.api.client.apache.ApacheHttpTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.UrlEncodedContent;

/**
 * Provides execution methods to do REST interaction with Atom feeds
 * 
 * @author pablo.gonzalez@11870.com
 * 
 */
public final class AtomService {

	static {
		HttpTransport.setLowLevelHttpTransport(ApacheHttpTransport.INSTANCE);
	}

	private static Logger log = OosLogger.getOOSLogger();

	private static Map<String, Object> cache = new WeakHashMap<String, Object>();

	/**
	 * 
	 * Enumeration used the determine the methods to be used.
	 * 
	 * @author pablo.gonzalez@11870.com
	 * 
	 */
	private static enum Method {
		Get, Post, Delete, Put;

		public void log(GenericUrl url) {
			log.info(name() + ": " + url.toString());
		}

		public void logCached(GenericUrl url) {
			log.info("Cached: " + name() + ": " + url.toString());
		}
		
		public HttpResponse execute(AuthenticationHandler authHandler, HttpTransport transport, GenericUrl url)
		throws IOException, HttpStatusException {
			return execute(authHandler, transport, url, null, null);
		}

		/**
		 * 
		 * Same as execute(HttpTransport, GenericUrl, Object) but without the
		 * data object.
		 * 
		 */
		public HttpResponse execute(AuthenticationHandler authHandler, 
				HttpTransport transport, GenericUrl url, List<OptionsMap> parameters)
				throws IOException, HttpStatusException {
			return execute(authHandler, transport, url, null, parameters);
		}

		/**
		 * 
		 * Creates a new {@link HttpRequest} applies current method, URL and (if
		 * provided) a data object. Finally returns the {@link HttpResponse}
		 * from the created request.
		 * 
		 * @param transport
		 *            An {@link HttpTransport} to initiate the request.
		 * @param url
		 *            a {@link GenericUrl} to use for the requests
		 * @param data
		 *            Key/Value object (see google-api-client)
		 * @return the resultant {@link HttpResponse}
		 * @throws IOException
		 * @throws HttpStatusException
		 */
		public HttpResponse execute(AuthenticationHandler authHandler, HttpTransport transport, GenericUrl url,
				HttpContent content, List<OptionsMap> parameters) throws IOException, HttpStatusException {
			return authHandler.getResponse(transport, url, name().toUpperCase(), content, parameters);
		}

	}
	
	public synchronized static HttpResponse executeRawGet(
			AuthenticationHandler authHandler, HttpTransport transport, GenericUrl url, List<OptionsMap> parameters)
			throws IOException, HttpStatusException, Exception {
		HttpResponse response = Method.Get.execute(authHandler, transport, url, parameters);
		Method.Get.log(url);
		return response;
	}

	/**
	 * 
	 * Used to retrieve information from the 11870.com API. The provided
	 * extension of {@link AtomEntity} is used to parse the result of the
	 * request.
	 * 
	 * @param <F>
	 * @param transport
	 * @param url
	 * @param clazz
	 * @return
	 * @throws IOException
	 * @throws HttpStatusException
	 */
	@SuppressWarnings("unchecked")
	public synchronized static <F extends AtomEntity> F executeGet(
			AuthenticationHandler authHandler, HttpTransport transport, GenericUrl url, List<OptionsMap> parameters, Class<? extends F> clazz)
			throws IOException, HttpStatusException, Exception {
		F result = null;
		Object object = null;
		// check if in cache
		if (cache.containsKey(url.toString())) {
			object = cache.get(url.toString());
		}
		if (object != null && object.getClass().equals(clazz)) {
			Method.Get.logCached(url);
			result = (F) cache.get(url.toString());
		} else {
			HttpResponse response = executeRawGet(authHandler, transport, url, parameters);
			if (response != null) {
				result = response.parseAs(clazz);
			}
			if (result instanceof OosEntry) { // Don't cache feed results
				cache.put(url.toString(), result);
			}
		}
		return result;
	}

	/**
	 * 
	 * This method is used to add new data to the passed url.
	 * 
	 * @param transport
	 * @param url
	 * @param data
	 * @return
	 * @throws IOException
	 * @throws HttpStatusException
	 */
	public synchronized static <F extends AtomEntity> F executePost(AuthenticationHandler authHandler, HttpTransport transport,
			GenericUrl url, HttpContent content, List<OptionsMap> parameters,  Class<? extends F> clazz) throws IOException,
			HttpStatusException {
		HttpResponse response = Method.Post.execute(authHandler, transport, url, content, parameters);
		Method.Post.log(url);
		if (response != null && response.isSuccessStatusCode) {
			if (content instanceof UrlEncodedContent) {
				try {
					return clazz.newInstance();
				} catch (Exception e) {
					return null;
				}
			} else {
				return response.parseAs(clazz);
			}
		} else {
			return null;
		}
	}

	/**
	 * 
	 * Removes the item located in the provided {@link GenericUrl}.
	 * 
	 * @param transport
	 * @param url
	 * @return
	 * @throws IOException
	 * @throws HttpStatusException
	 */
	public synchronized static boolean executeDelete(AuthenticationHandler authHandler, HttpTransport transport,
			GenericUrl url) throws IOException, HttpStatusException {
		HttpResponse response = Method.Delete.execute(authHandler, transport, url);
		Method.Delete.log(url);
		cache.remove(url.toString());
		return response.isSuccessStatusCode;
	}

	/**
	 * 
	 * This method is used to edit the data contained in the item located at the
	 * provided {@link GenericUrl}
	 * 
	 * @param transport
	 * @param url
	 * @param data
	 * @return
	 * @throws IOException
	 * @throws HttpStatusException
	 */
	public synchronized static boolean executePut(AuthenticationHandler authHandler, HttpTransport transport,
			GenericUrl url, HttpContent content) throws IOException,
			HttpStatusException {
		HttpResponse response = Method.Put.execute(authHandler, transport, url, content, null);
		Method.Put.log(url);
		cache.remove(url.toString());
		return response.isSuccessStatusCode;
	}

	/**
	 * 
	 * This exception is used to inform of an error with the request. Normally
	 * when the status code of the request is >=300.
	 * 
	 * @author pablo.gonzalez@11870.com
	 * 
	 */
	public static class HttpStatusException extends Exception {

		private static final long serialVersionUID = -1570038483509158408L;

		private int statusCode;

		private HttpResponse response;

		public HttpStatusException(int statusCode) {
			this(statusCode, null);
		}

		public HttpStatusException(int statusCode, String extraMessage) {
			super(((extraMessage != null) ? extraMessage : Integer
					.toString(statusCode)));
			this.statusCode = statusCode;
		}

		public int getStatusCode() {
			return statusCode;
		}

		public void setResponse(HttpResponse response) {
			this.response = response;
		}
		
		public HttpResponse getResponse() {
			return response;
		}

	}

}
